local rtk=require('libluartk')
local gx={}
gx.bit32=rtk.bit32
local bit64=rtk.bit64
local g_interpreters={}

function gx.addInterpreter(intp)
	table.insert(g_interpreters,intp)
end

function gx.getInterpreters()
	return g_interpreters
end

function gx.tohex(buf)
	local bytes={}
	local i
	for i=1,#buf do
		local b=string.byte(buf,i)
		table.insert(bytes,string.format('%02x',b))
	end
	return table.concat(bytes,' ')
end

function gx.int_to_hex(num,N)
	local bytes = ''
	assert(N==2 or N==4 or N==8,'Width must be 2/4/8')
	for i = 1, N do
		bytes = bytes .. string.char(bit64.band(bit64.rshift(num, (i-1) * 8), 0xFF))
	end
	return bytes
end

function gx.hexdump(buf, addr)
	-- 生成列编号表头
	local header = "				  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F"
	local result = {header, string.rep("-", 75)} -- 添加分隔线
	
	local bytes_per_line = 16
	local lines = math.ceil(#buf / bytes_per_line)
	
	for i = 0, lines - 1 do
		local offset = i * bytes_per_line
		-- 生成 64 位偏移量（16 进制，16 个字符宽度，前导零）
		local offset_str = string.format("%016X", addr + offset)
		
		-- 生成十六进制部分
		local hex_part = ""
		-- 生成 ASCII 部分
		local ascii_part = ""
		
		for j = 1, bytes_per_line do
			local idx = offset + j
			if idx <= #buf then
				local byte = string.byte(buf, idx)
				hex_part = hex_part .. string.format("%02X ", byte)
				-- 控制字符显示为 '.'，可打印字符正常显示
				ascii_part = ascii_part .. (byte >= 32 and byte <= 126 and string.char(byte) or '.')
			else
				hex_part = hex_part .. "   "
				ascii_part = ascii_part .. " "
			end
			
			-- 在每 8 个字节后添加一个额外的空格
			if j == 8 then
				hex_part = hex_part .. " "
				ascii_part = ascii_part .. " "
			end
		end
		
		-- 每行的最终格式：偏移量 + 十六进制部分 + ASCII 部分
		result[#result + 1] = string.format("%s  %-49s	|%s|", offset_str, hex_part, ascii_part)
	end
	
	return table.concat(result, "\n")
end

function gx:peek(addr,size)
	if not self.FhProcess then
		return
	end
	size=size or 4
	local buf=self.readProcessMemory(self.FhProcess,addr,size)
	return buf
end

function gx:poke(addr,buf)
	if not self.FhProcess then
		return
	end
	return self.writeProcessMemory(self.FhProcess,addr,buf,#buf)
end

function gx.__obj_to_table(obj)
	local function copy_table_expand(src)
		local tgt={}
		rtk.table_each(src,function(k,v)
			local tp=type(v)
			if tp=='userdata' then
				tgt[k]=v:toTable()
			elseif tp=='table' then
				tgt[k]=copy_table_expand(v)
			else
				tgt[k]=v
			end
		end)
		return tgt
	end
	local def=obj.FDef
	local tbl={}
	if def.type=='Array' then
		local i
		for i=1,def.size do
			tbl[i]=obj[i]
		end
	else
		rtk.table_ieach(def.items,function(_,it)
			if it.name then
				tbl[it.name]=obj[it.name]
			end
		end)
	end
	return copy_table_expand(tbl)
end

function gx.__type_to_prop_sheet(def,B)
	local psheet=B.TPropertyGrid{children={},FPath=''}
	local pid=0
	local function newid()
		pid=pid+1
		return string.format('Prop_' .. pid)
	end
	local props={}
	psheet.FProperties=props
	local function handle_item(pprop,it,key)
		local typeDesc=it.type
		if not it.name then
			return
		end
		key=key or it.name
		local path
		if type(key)=='string' then
			path=string.format('%s["%s"]',pprop.FPath,key)
		else
			path=string.format('%s[%d]',pprop.FPath,key)
		end
		local prop=B.TProperty{label=tostring(key),id=newid(),FPath=path}
		local btype=typeDesc.base_type or typeDesc.type
		assert(btype,string.format('Invalid type "%s" for "%s"',
			typeDesc.type,it.name))
		if btype=='Array' or btype=='Struct' then
			prop.ptype='group'
			prop.children={}
			if typeDesc.type=='Struct' then
				rtk.table_ieach(typeDesc.items,function(_,sit)
					handle_item(prop,sit)
				end)
			elseif typeDesc.type=='Array' then
				local i
				local tmp_type={name=it.name,type=typeDesc.ele_type}
				for i=1,typeDesc.size do
					handle_item(prop,tmp_type,i)
				end
			else
				assert(nil,string.format('Invalid type "%s" for "%s"',
					typeDesc.type,it.name))
			end
		elseif typeDesc.type=='Enum' then
			prop.ptype='enum'
			prop.labels=typeDesc.labels
			prop.values=typeDesc.values
			table.insert(props,prop)
		else
			-- a single item
			if btype=='string' or btype=='hex' or btype=='char' then
				prop.ptype='string'				
			elseif btype:match('int.*') or btype:match('uint.*') then
				prop.ptype='int'
			elseif btype=='bool' then
				prop.ptype='bool'
			else
				assert(nil,string.format('Invalid type "%s" for "%s"',
					typeDesc.type,it.name))
			end
			table.insert(props,prop)
		end
		table.insert(pprop.children,prop)
		return prop
	end
	rtk.table_ieach(def.items,function(_,it)
		handle_item(psheet,it)
	end)

	function psheet:doRefresh()
		self:Freeze()
		local obj=self.FObj
		local code={}
		rtk.table_ieach(self.FProperties,function(_,p)
			local line=string.format('_Grid:GetProperty("%s").Value=_obj_%s',p.id,p.FPath)
			table.insert(code,line)
		end)
		local ok,err
		code=table.concat(code,'\n')
--		tprint(code)
		local env={_Grid=self:GetGrid(),_obj_=obj,print=print}
		code,err=load(code,'','t',env)
		assert(code,err)
		ok,err=pcall(code)
		self:Thaw()
		assert(ok,err)
	end
	function psheet:doCreate(parent,obj)
		self.FProperties=props
		if obj then
			self.FObj=obj
			self:doRefresh()
		end
	end
	function psheet:onPgChanged(event)
		local obj=self.FObj
		local prop=event:GetProperty()
		local nval=prop.Value:GetString()
		local ok,err
		local code
		if prop.F_Def.ptype=='bool' then
			nval=(nval=='1' and 'true') or 'false'
			code=string.format('obj%s=%s',prop.F_Def.FPath,nval)
		else
			code=string.format('obj%s="%s"',prop.F_Def.FPath,nval)
		end
		rtk.dbg_print('code=%s\n',code)
		code,err=load(code,'','t',{obj=obj})
		assert(code,err)
		ok,err=pcall(code)
		assert(ok,err)
		event:Skip()
	end
	return psheet
end

function gx.__obj_to_prop_sheet(obj,B)
	local pgrid=gx.__type_to_prop_sheet(obj.FDef,B)
	pgrid.FObj=obj
	return pgrid
end

function gx.object_editor(tdef,options)
	local B=require('wxExt.ui_builder')
	local DBG=require('wxExt.debug')
	local gx=require('libgamex')
	local buttons=options.buttons
	local title0=options.title or 'Object editor'
	local DlgDef=B.TDialog{
		minsize=options.minsize,
		sizeable=true,autoFit=true,
		gx.__type_to_prop_sheet(tdef,B){
			id='ps_properties',expand=true,proportion=1
		},
		B.THBox{
			expand=true,
			B.TStretch{proportion=1},
			B.TButton{id='Ok'},
			B.TButton{id='Reload'},
			B.TButton{id='Apply'},
			buttons.previous and B.TButton{id='Previous'},
			buttons.next and B.TButton{id='Next'},
		},
		rtk.getpropFlag('debug') and DBG.ID_DEBUG_BAR{id='dbar'},
		Ok_onClick=function(self)
			self:Apply_onClick()
			self:Close()
		end,
		doRefresh=function(self)
			self.ps_properties:doRefresh()
			self.Title=string.format('%s(0x%x)',title0,self.FAddr)
		end,
		Reload_onClick=function(self)
			self:doReload()
		end,
		Apply_onClick=function(self)
			self:doApply()
		end,
		Previous_onClick=buttons.previous and function(self)
			self.FAddr=self.FAddr-self.FSize
			self:doReload()
		end,
		Next_onClick=buttons.next and function(self)
			self.FAddr=self.FAddr+self.FSize
			self:doReload()
		end,
		doReload=function(self)
			local addr=self.FAddr
			rtk.dbg_print('Reading %d bytes from %x\n',self.FSize,addr)
			self.FBuffer:load(gx:peek(addr,self.FSize))
			self:doRefresh()
		end,
		doApply=function(self)
			local addr=self.FAddr
			local obj=self.F_Env.FObj
			local data=self.FBuffer:toString()
			rtk.printk(2,'Save %d bytes to %x\n',#data,addr)
			gx:poke(addr,data)
		end,
		onCreate=function(self,addr)
			self.FAddr=addr
			self.FSize=tdef.FSize
			local buf=gx.CBuffer.create()
			buf:load(gx:peek(addr,tdef.FSize))
			local obj=gx.CAccessor.create(tdef)
			obj:bind(buf)
			local encoding=options.encoding
			if encoding then
				obj:setEncoding(encoding)
			end
			self.FBuffer=obj.buffer
			self.F_Env.FObj=obj
			self.ps_properties.FObj=obj
			self:doRefresh()
			self:Fit()
			self:SetMinSize(self:GetSize())
		end
	}
	return DlgDef
end

return gx
